#include "ws2812.h"
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>
#include <fcntl.h>

namespace ws2812 {

#define SPI_DEVICE_NAME "/dev/spidev2.0"
static int gs_fd;

uint8_t spi_init(char *name, int *fd, int mode, uint32_t freq)
{
    int i;

    /* open the spi device */
    *fd = open(name, O_RDWR);
    if ((*fd) < 0)
    {
        perror("spi: open failed.\n");

        return 1;
    }
    else
    {
        /* set the spi write mode */
        i = mode;
        if (ioctl(*fd, SPI_IOC_WR_MODE, &i) < 0)
        {
            perror("spi: write mode set failed.\n");

            return 1;
        }

        /* set the spi read mode */
        if (ioctl(*fd, SPI_IOC_RD_MODE, &i) < 0)
        {
            perror("spi: read mode set failed.\n");

            return 1;
        }

        /* set the spi write frequence */
        i = freq;
        if (ioctl(*fd, SPI_IOC_WR_MAX_SPEED_HZ, &i) < 0)
        {
            perror("spi: set spi write speed failed.\n");

            return 1;
        }

        /* set the spi read frequence */
        if (ioctl(*fd, SPI_IOC_RD_MAX_SPEED_HZ, &i) < 0)
        {
            perror("spi: set spi read speed failed.\n");

            return 1;
        }

        /* set the spi write MSB first */
        i = 0;
        if (ioctl(*fd, SPI_IOC_WR_LSB_FIRST, &i) < 0)
        {
            perror("spi: set spi write msb first failed.\n");

            return 1;
        }

        /* set the spi read MSB first */
        if (ioctl(*fd, SPI_IOC_RD_LSB_FIRST, &i) < 0)
        {
            perror("spi: set spi read msb first failed.\n");

            return 1;
        }

        /* set the spi write 8 bits mode */
        i = 8;
        if (ioctl(*fd, SPI_IOC_WR_BITS_PER_WORD, &i) < 0)
        {
            perror("spi: set spi wirte 8 bit failed.\n");

            return 1;
        }

        /* set the spi read 8 bits mode */
        if (ioctl(*fd, SPI_IOC_RD_BITS_PER_WORD, &i) < 0)
        {
            perror("spi: set spi read 8 bit failed.\n");

            return 1;
        }

        return 0;
    }
}

uint8_t ws2812b_init(void)
{
    return spi_init((char *)SPI_DEVICE_NAME, &gs_fd, SPI_MODE_3, 1000 * 1000 * 10);
}

void ws2812b_write_one_frame(uint32_t rgb, uint8_t temp[48])
{
    uint8_t r, g, b;
    uint8_t i, j;
    uint32_t c, point;
    const uint16_t one_code = 0xFFF8U;
    const uint16_t zero_code = 0xE000U;

    r = (uint8_t)((rgb >> 16) & 0xFF); /* set red */
    g = (uint8_t)((rgb >> 8) & 0xFF); /* set green */
    b = (uint8_t)((rgb >> 0) & 0xFF); /* set blue */
    c = ((uint32_t)(g) << 16) | ((uint32_t)(r) << 8) | b; /* set color */

    memset(temp, 0, sizeof(uint8_t) * 30); /* clear the temp buffer */

    point = 0; /* clear point */
    for (i = 0; i < 24; i++) /* set 24 bit */
    {
        if (((c >> (23 - i)) & 0x01) != 0) /* if bit 1 */
        {
            for (j = 0; j < 16; j++) /* 16 bit */
            {
                if (((one_code >> (15 - j)) & 0x01) != 0) /* if one code */
                {
                    temp[point / 8] |= 1 << (7 - (point % 8)); /* set bit 1 */
                } else {
                    temp[point / 8] |= 0 << (7 - (point % 8)); /* set bit 0 */
                }
                point = point + 1; /* point++ */
            }
        } else /* if bit 0 */
        {
            for (j = 0; j < 16; j++) /* 16 bit */
            {
                if (((zero_code >> (15 - j)) & 0x01) != 0) /* if zero code */
                {
                    temp[point / 8] |= 1 << (7 - (point % 8)); /* set bit 1 */
                } else {
                    temp[point / 8] |= 0 << (7 - (point % 8)); /* set bit 0 */
                }
                point = point + 1; /* point++ */
            }
        }
    }
}

uint8_t spi_write_cmd(uint8_t *buf, uint16_t len)
{
    struct spi_ioc_transfer k;
    int l;

    /* clear ioc transfer */
    memset(&k, 0, sizeof(k));

    /* set the param */
    k.tx_buf = (unsigned long)buf;
    k.len = len;
    k.cs_change = 0;

    /* transmit */
    l = ioctl(gs_fd, SPI_IOC_MESSAGE(1), &k);
    if (l != len)
    {
        perror("spi: length check error.\n");

        return 1;
    }

    return 0;
}
int write_color(int color) {
    const int max_leds = 15;
    uint8_t color_buff[1024] = {'\0'};
    for (int i = 0; i < max_leds; i ++) {
        ws2812b_write_one_frame(color, &color_buff[i * 48]);
    }
    return spi_write_cmd(color_buff, max_leds * 48);
}

}
